package com.example.understandvideo.service.transcode;

import com.example.understandvideo.mapper.*;
import com.example.understandvideo.pojo.*;
import com.example.understandvideo.pojo.request.ClipTaskRequest;
import com.example.understandvideo.pojo.request.CommonSearchRequest;
import com.example.understandvideo.pojo.request.MarathonTaskSaveRequest;
import com.example.understandvideo.pojo.request.MarathonTaskSearchRequest;
import com.example.understandvideo.pojo.response.CommonResponse;
import com.example.understandvideo.pojo.response.DetailResponse;
import com.example.understandvideo.service.CommonService;
import com.example.understandvideo.tool.*;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
import lombok.Value;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.http.*;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Type;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.util.*;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static java.lang.Thread.sleep;

@Slf4j
@Component
public class SportProcessor {

    private final BlockingQueue<MarathonTask> queue = new LinkedBlockingQueue<>(100);
    private volatile boolean running = true;
    private final int THREAD_COUNT = 10;
    @Resource
    private MarathonTaskDao marathonTaskDao;
    @Resource
    private MarathonPlayerDao marathonPlayerDao;
    @Resource
    private MarathonGroupDao marathonGroupDao;
    @Resource
    private MarathonResultDao marathonResultDao;
    @Resource
    private CommonService commonService;
    @Resource
    private TranscodeService transcodeService;


    /**
     * 创建线程池，核心10，最大核心30，存活5秒，队列存储2000，拒绝策略直接抛出异常
     */
    private final ThreadPoolExecutor threadPool = new ThreadPoolExecutor(THREAD_COUNT, 30,
            5, TimeUnit.SECONDS, new LinkedBlockingQueue<>(100),
            new ThreadPoolExecutor.CallerRunsPolicy());

    public SportProcessor() {
        for (int i = 0; i < THREAD_COUNT; i++) {
            threadPool.execute(new ResultWorker());
        }
    }


    public CommonResponse list(MarathonTaskSearchRequest request) {
        Long total = marathonTaskDao.count(request);
        CommonResponse<MarathonTask> response = new CommonResponse();
        if (Objects.isNull(total) || total == 0) {
            return response;
        }
        response.setTotal(total);
        response.setResponseList(marathonTaskDao.search(request));
        return response;
    }

    public DetailResponse detail(Long id) {
        DetailResponse response = new DetailResponse();
        MarathonTask task = marathonTaskDao.selectByPrimaryKey(id);
        if (Objects.nonNull(task)) {
            BeanUtils.copyProperties(task, response);
            List<MarathonResult> results = marathonResultDao.selectByTaskId(task.getId());
            if (!CollectionUtils.isEmpty(results)) {
                response.setResults(results);
            }
        }
        return response;
    }

    public ResultFul clipTask(ClipTaskRequest request) {
        Gson gson = new Gson();
        if (Objects.isNull(request.getType()) || request.getType() == 1) {

            MarathonResult result = null;
            if (Objects.isNull(request.getResultId())) {
                result = new MarathonResult();
                result.setResult(gson.toJson(request.getClipPoints()));
                result.setTaskId(request.getTaskId());
                result.setGroupId(request.getGroupId());
                result.setPlayerId(request.getPlayerId());
                marathonResultDao.insert(result);
                request.setResultId(result.getId());
            } else {
                result = marathonResultDao.selectByPrimaryKey(request.getResultId());
            }
            File file = new File(request.getFilepath());
            String outDirPath = commonService.getWorkSpace() + "clip" + File.separator + LocalDate.now() + File.separator + request.getTaskId() + File.separator;
            //String outFilepath = commonService.getWorkSpace() + "clip" + File.separator + LocalDate.now() + File.separator + request.getTaskId() + File.separator + "clips-" + name + "-" + file.getName();
            //VideoFrameExtractor extractor = new VideoFrameExtractor(commonService.getFfmpegExcFileName());
            try {
                //extractor.clipVideo(request.getFilepath(), outDirPath, outFilepath, request.getClipPoints());
                List<TimeSlice> times = new ArrayList<>();
                //处理时间
                String resultStr = result.getResult();
                if (!StringUtils.isBlank(resultStr)) {
                    Type listType = new TypeToken<List<MarathonPlayerPoints>>() {
                    }.getType();
                    List<MarathonPlayerPoints> list = gson.fromJson(resultStr, listType);
                    request.setClipPoints(list);
                }
                for (MarathonPlayerPoints entity : request.getClipPoints()) {
                    TimeSlice timeSlice = new TimeSlice();
                    timeSlice.setStart(DateFormatUtil.formatDateToHHmmssV2(entity.getStartTime(), Boolean.TRUE));
                    timeSlice.setEnd(DateFormatUtil.formatDateToHHmmssV2(entity.getEndTime(), Boolean.TRUE));
                    times.add(timeSlice);
                }
                CutCloudTaskResponse response = transcodeService.sendCutCloudTaskOne(times, file.getAbsolutePath(), outDirPath);
                do {
                    sleep(1000);
                    String body = HttpTool.getXmlStr(commonService.getServerAddress() + "/api/task/" + response.getCloudId() + "/progress");
                    CloudResults resultTask = (CloudResults) XMLUtil.convertXmlStrToObject(CloudResults.class, body);
                    String status = resultTask.getResults().get(0).getStatus();
                    if (StringUtils.isNotBlank(status) && status.contains("ERROR")) {
                        return ResultFul.Error("视频合并转码生成失败！");
                    } else if (StringUtils.isNotBlank(status) && status.contains("COMPLETED")) {
                        result.setFilepath(response.getTargetPath());
                        marathonResultDao.updateFilePath(result);
                        return ResultFul.OK(response.getTargetPath());
                    }
                } while (true);
            } catch (Exception e) {
                log.error("clipTask error={}", e.getMessage());
            }
            return ResultFul.Error("生成失败！");
        } else {
            if (request.getType() == 2) {
                MarathonTaskSearchRequest searchRequest = new MarathonTaskSearchRequest();
                searchRequest.setPageSize(1000);
                List<MarathonTask> taskList = marathonTaskDao.search(searchRequest);
                if (CollectionUtils.isEmpty(taskList)) {
                    return ResultFul.Error("系统内无任务！");
                }
                List<ClipTaskRequest.CutTask> cutTaskList = new ArrayList<>();
                taskList.forEach(task -> {
                    ClipTaskRequest.CutTask cutTask = new ClipTaskRequest.CutTask();
                    cutTask.setId(task.getId());
                    cutTask.setIndex(task.getId());
                    cutTaskList.add(cutTask);
                });
                return dealCutV2(cutTaskList, request.getPlayerId());
            } else {
                return dealCutV2(request.getTaskList(), request.getPlayerId());
            }

        }
    }

    ResultFul dealCut(List<ClipTaskRequest.CutTask> cutTaskList, Long playerId) {
        try {
            String tempDirPath = commonService.getWorkSpace() + "clip" + File.separator + LocalDate.now() + File.separator + playerId + File.separator;
            String outFilepath = commonService.getWorkSpace() + "clip" + File.separator + LocalDate.now() + File.separator + playerId + File.separator + playerId + "-clips" + "-剪辑结果.mp4";
            VideoFrameExtractor extractor = new VideoFrameExtractor(commonService.getFfmpegExcFileName());
            // 创建临时目录
            File tempDir = new File(tempDirPath);
            if (!tempDir.exists()) {
                tempDir.mkdirs();
            }
            // 创建片段列表文件
//            File listFile = new File(tempDir, "filelist.txt");
//            cutTaskList.sort(Comparator.comparing(ClipTaskRequest.CutTask::getIndex));
//            Integer number = 0;
//            try (BufferedWriter writer = new BufferedWriter(new FileWriter(listFile))) {
//                for (ClipTaskRequest.CutTask cutTask : cutTaskList) {
//                    MarathonResult result = marathonResultDao.selectByTaskIdAndPlayerId(cutTask.getId(), playerId);
//                    if (Objects.nonNull(result) && Objects.nonNull(result.getFilepath())) {
//                        writer.write("file '" + result.getFilepath() + "'\n");
//                        number++;
//                    }
//                }
//            }
            String value = "";
            String filterComplex = "";
            cutTaskList.sort(Comparator.comparing(ClipTaskRequest.CutTask::getIndex));
            Integer number = 0;
            for (ClipTaskRequest.CutTask cutTask : cutTaskList) {
                MarathonResult result = marathonResultDao.selectByTaskIdAndPlayerId(cutTask.getId(), playerId);
                if (Objects.nonNull(result) && Objects.nonNull(result.getFilepath())) {
                    value = value + "-i " + result.getFilepath() + " ";
                    filterComplex = filterComplex + "[" + number + ":v][" + number + ":a]";
                    number++;
                }
            }
            if (number == 0) {
                return ResultFul.Error("目标任务列表内无该号码牌的剪辑结果！");
            }
            filterComplex = "\"" + filterComplex + "concat=n=" + number + ":v=1:a=1\"";
            // 合并所有片段
            System.out.println("正在合并所有片段...");
//            executeCommand(
//                    commonService.getFfmpegExcFileName(), "-y",
//                    "-f", "concat",
//                    "-safe", "0",
//                    "-i", listFile.getAbsolutePath(),
//                    "-c", "copy",
//                    outFilepath
//            );
            System.out.println(value);
            System.out.println(filterComplex);
            System.out.println(outFilepath);
            executeCommand(
                    commonService.getFfmpegExcFileName(), value,
                    "-filter_complex", filterComplex,
                    "-c", "copy",
                    outFilepath
            );
            return ResultFul.OK(outFilepath);
        } catch (IOException e) {
            log.error("clipTask error={}", e.getMessage());
        } catch (InterruptedException e) {
            log.error("clipTask InterruptedException={}", e.getMessage());
        }
        return ResultFul.Error("生成失败！");
    }

    ResultFul dealCutV2(List<ClipTaskRequest.CutTask> cutTaskList, Long playerId) {
        try {
            String tempDirPath = commonService.getWorkSpace() + "clip" + File.separator + LocalDate.now() + File.separator + playerId + File.separator;
            // 创建临时目录
            File tempDir = new File(tempDirPath);
            if (!tempDir.exists()) {
                tempDir.mkdirs();
            }
            List<ClipData> clipDataList = new ArrayList<>();
            cutTaskList.sort(Comparator.comparing(ClipTaskRequest.CutTask::getIndex));
            Integer number = 0;
            VideoFrameExtractor extractor = new VideoFrameExtractor(commonService.getFfmpegExcFileName());
            for (ClipTaskRequest.CutTask cutTask : cutTaskList) {
                MarathonResult result = marathonResultDao.selectByTaskIdAndPlayerId(cutTask.getId(), playerId);
                if (Objects.nonNull(result) && Objects.nonNull(result.getFilepath())) {
                    double duration = extractor.getVideoDuration(result.getFilepath());
                    if (duration == -1) {
                        continue;
//                        duration = 1;
                    }
                    ClipData clipData = new ClipData();
                    if (!CollectionUtils.isEmpty(clipDataList)) {
                        clipData.setClipInsertTime(clipDataList.get(0).getClipStopTime());
                    }
                    clipData.setClipStartTime("00:00:00");
                    clipData.setClipStopTime(DateFormatUtil.formatDateToHHmmssV2((long) (duration * 1000), Boolean.TRUE));
                    clipData.setClipFilePath(result.getFilepath());
                    clipData.setId(number);
                    clipDataList.add(clipData);
                    number++;
                }
            }
            if (number == 0) {
                return ResultFul.Error("目标任务列表内无该号码牌的剪辑结果！");
            }

            CutCloudTaskResponse response = transcodeService.sendCutCloudTask(tempDirPath, clipDataList);
            do {
                sleep(1000);
                String body = HttpTool.getXmlStr(commonService.getServerAddress() + "/api/task/" + response.getCloudId() + "/progress");
                CloudResults resultTask = (CloudResults) XMLUtil.convertXmlStrToObject(CloudResults.class, body);
                String status = resultTask.getResults().get(0).getStatus();
                if (StringUtils.isNotBlank(status) && status.contains("ERROR")) {
                    return ResultFul.Error("视频合并转码生成失败！");
                } else if (StringUtils.isNotBlank(status) && status.contains("COMPLETED")) {
                    return ResultFul.OK(response.getTargetPath());
                }
            } while (true);
        } catch (Exception e) {
            log.error("clipTask error={}", e.getMessage());
        }
        return ResultFul.Error("生成失败！");
    }

    private void executeCommand(String... command) throws IOException, InterruptedException {
        ProcessBuilder processBuilder = new ProcessBuilder(command);
        Process process = processBuilder.start();

        // 读取标准输出和错误输出
        Thread stdOutThread = new Thread(() -> {
            java.util.Scanner s = new java.util.Scanner(process.getInputStream());
            while (s.hasNextLine()) {
                System.out.println(s.nextLine());
            }
            s.close();
        });

        Thread errOutThread = new Thread(() -> {
            java.util.Scanner s = new java.util.Scanner(process.getErrorStream());
            while (s.hasNextLine()) {
                System.err.println(s.nextLine());
            }
            s.close();
        });

        stdOutThread.start();
        errOutThread.start();

        int exitCode = process.waitFor();
        stdOutThread.join();
        errOutThread.join();

        if (exitCode != 0) {
            throw new IOException("命令执行失败，退出代码: " + exitCode);
        }
    }

    public void addPlayer(MarathonPlayer model) {
        marathonPlayerDao.insert(model);
    }

    public CommonResponse searchPlayer(CommonSearchRequest request) {
        Long total = marathonPlayerDao.count(request);
        CommonResponse<MarathonPlayer> response = new CommonResponse();
        if (Objects.isNull(total) || total == 0) {
            return response;
        }
        response.setTotal(total);
        response.setResponseList(marathonPlayerDao.search(request));
        return response;
    }

    public void addGroup(MarathonGroup model) {
        marathonGroupDao.insert(model);
    }

    public CommonResponse searchGroup(CommonSearchRequest request) {
        Long total = marathonGroupDao.count(request);
        CommonResponse<MarathonGroup> response = new CommonResponse();
        if (Objects.isNull(total) || total == 0) {
            return response;
        }
        response.setTotal(total);
        List<MarathonGroup> responseList = marathonGroupDao.search(request);
        response.setResponseList(responseList);
        return response;
    }

    public void put(MarathonTaskSaveRequest request) {
        try {
            MarathonTask task = new MarathonTask();
            task.setName(request.getName());
            task.setFilepath(request.getFilepath());
            task.setGroupId(request.getGroupId());
            task.setCode(FileUtils.getFileMD5(request.getFilepath()));
            task.setStatus(0);
            marathonTaskDao.insert(task);
            this.queue.put(task);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    @PreDestroy
    public void stop() throws InterruptedException {
        this.running = false;
        //ExecutorUtil.shutdown(threadPool);
    }

    /**
     * 使用通用识别
     *
     * @param filepath
     * @return
     */
    public List<MarathonSmartResult> marathonVideoUnderstandV1(String filepath, List<String> playerList) {
        String model = commonService.getAIModelName();
        List<MarathonSmartObject> picList = new ArrayList<>();
        File file = new File(filepath);
        String name = file.getName().substring(0, file.getName().length() - 4);
        String targetPicPath = commonService.getWorkSpace() + "thumbnail" + File.separator + LocalDate.now() + File.separator + name + File.separator;
        File targetPicDir = new File(targetPicPath);
        if (!targetPicDir.exists()) {
            targetPicDir.mkdirs();
        }
        try {
            VideoFrameExtractor extractor = new VideoFrameExtractor(commonService.getFfmpegExcFileName());
            // 每秒抽取1帧
            int framesPerSecond = 1;
            // 调用方法抽取帧
            List<String> frames = extractor.extractFramesV4(filepath, targetPicPath, framesPerSecond);
            for (String frame : frames) {
                MarathonSmartObject object = new MarathonSmartObject();
                object.setSecond(commonService.extractAndConvert(frame));
                object.setPicture(frame);
                picList.add(object);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        /**
         * 遍历号牌
         */
        List<MarathonSmartResult> resultList = new ArrayList<>();
        String playerListString = String.join(",", playerList);
        for (MarathonSmartObject object : picList) {
            try {
                MarathonSmartResult smartResult = new MarathonSmartResult();
                log.info("第 {} 秒", object.getSecond());
                String result = sendSmartRequest(model, Tools.readImage(object.getPicture()), playerListString);
                smartResult.setResult(result);
                smartResult.setSecond(object.getSecond());
                resultList.add(smartResult);
            } catch (Exception e) {
                MarathonSmartResult smartResult = new MarathonSmartResult();
                smartResult.setErrorResult("第 " + object.getSecond() + " 秒的图片大模型识别错误");
                smartResult.setSecond(object.getSecond());
                resultList.add(smartResult);
                e.printStackTrace();
            }
        }
        return resultList;
    }

    String sendSmartRequest(String model, String picCode, String playerList) {
        Gson gson = new Gson();
        //参数
        Request request = new Request();
        request.setModel(model);
        Message message = new Message();
        message.setRole("user");
        List<Content> content = new ArrayList<>();
        Content question = new Content();
        question.setType("text");
        question.setText("这种图片中是否出现了如下编号的马拉松运动员,若出现了,请直接返回出现了的编号列表,若没有出现,请返回无\n 编号：" + playerList);
        content.add(question);
        Content pic = new Content();
        pic.setType("image_url");
        pic.setImage_url(new ImageUrl(picCode));
        content.add(pic);
        message.setContent(content);
        request.setMessages(Collections.singletonList(message));
        String gsonStr = gson.toJson(request);
        String result = HttpTool.post(gsonStr);
        return JsonUtils.extractContentFromStrJson(result);
    }

    private class ResultWorker implements Runnable {
        @Override
        public void run() {
            try {
                while (running) {
                    MarathonTask model = queue.take();
                    List<MarathonPlayer> marathonPlayerList = marathonPlayerDao.selectAll();
                    if (CollectionUtils.isEmpty(marathonPlayerList)) {
                        model.setStatus(1);
                        marathonTaskDao.updateByPrimaryKey(model);
                        return;
                    }
                    try {
                        List<String> playerList = marathonPlayerList.stream().map(MarathonPlayer::getName).collect(Collectors.toList());
                        List<MarathonSmartResult> resultList = marathonVideoUnderstandV1(model.getFilepath(), playerList);
                        if (CollectionUtils.isEmpty(resultList)) {
                            model.setStatus(2);
                            model.setUpdateTime(new Date());
                            marathonTaskDao.updateByPrimaryKey(model);
                            return;
                        }
                        Gson gson = new Gson();
                        //排序
                        resultList.sort(Comparator.comparing(MarathonSmartResult::getSecond));
                        List<String> errorLogList = resultList.stream().filter(result -> !StringUtils.isEmpty(result.getErrorResult())).map(MarathonSmartResult::getErrorResult).collect(Collectors.toList());
                        if (!CollectionUtils.isEmpty(errorLogList)) {
                            model.setLog(StringUtils.join(errorLogList, "\n"));
                        }
                        for (MarathonPlayer player : marathonPlayerList) {
                            List<MarathonPlayerPoints> playerPointsList = new ArrayList<>();
                            MarathonPlayerPoints lastPoint = new MarathonPlayerPoints();
                            for (MarathonSmartResult result : resultList) {
                                if (StringUtils.isNotEmpty(result.getResult()) && result.getResult().contains(player.getName())) {
                                    MarathonPlayerPoints points = new MarathonPlayerPoints();
                                    points.setId(player.getId());
                                    points.setStartTime(result.getSecond() * 1000);
                                    points.setEndTime((result.getSecond() + 1) * 1000);
                                    if (Objects.nonNull(lastPoint) && Objects.nonNull(lastPoint.getEndTime()) && Objects.equals(points.getStartTime(), lastPoint.getEndTime())) {
                                        points.setStartTime(lastPoint.getStartTime());
                                    }
                                    lastPoint = points;
                                } else {
                                    if (Objects.nonNull(lastPoint)) {
                                        if (Objects.nonNull(lastPoint.getStartTime()) && Objects.nonNull(lastPoint.getEndTime())) {
                                            playerPointsList.add(lastPoint);
                                            lastPoint = new MarathonPlayerPoints();
                                        }
                                    }
                                }
                            }
                            MarathonResult marathonResult = new MarathonResult();
                            marathonResult.setTaskId(model.getId());
                            marathonResult.setPlayerId(player.getId());
                            marathonResult.setGroupId(model.getGroupId());
                            marathonResult.setResult(gson.toJson(playerPointsList));
                            marathonResultDao.insert(marathonResult);
                            ClipTaskRequest request = new ClipTaskRequest();
                            request.setType(1);
                            request.setResultId(marathonResult.getId());
                            request.setFilepath(model.getFilepath());
                            clipTask(request);
                        }
                        model.setStatus(1);
                        model.setUpdateTime(new Date());
                        marathonTaskDao.updateByPrimaryKey(model);
                    } catch (Exception e) {
                        model.setStatus(2);
                        model.setUpdateTime(new Date());
                        marathonTaskDao.updateByPrimaryKey(model);
                        log.error("[FileObjectProcessor] sync data error,errorMsg={}", e.getMessage());
                    }
                }
            } catch (Exception ex) {
                log.error("[FileObjectProcessor] run error={}", ex.getMessage());
            }
        }
    }
}
